package net.ericaro.neojavagengen;

import java.beans.Introspector;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import net.ericaro.neojavagen.Literals;
import net.ericaro.neojavagen.blocks.Block;
import net.ericaro.neojavagen.blocks.BlockStatement;
import net.ericaro.neojavagen.blocks.BlockStatements;
import net.ericaro.neojavagen.blocks.ExpressionStatement;
import net.ericaro.neojavagen.blocks.ReturnStatement;
import net.ericaro.neojavagen.classes.ClassBody;
import net.ericaro.neojavagen.classes.ClassBodyDeclarations;
import net.ericaro.neojavagen.classes.ClassModifier;
import net.ericaro.neojavagen.classes.ConstructorBody;
import net.ericaro.neojavagen.classes.ConstructorDeclaration;
import net.ericaro.neojavagen.classes.ConstructorDeclarator;
import net.ericaro.neojavagen.classes.ConstructorModifier;
import net.ericaro.neojavagen.classes.FieldDeclaration;
import net.ericaro.neojavagen.classes.FieldModifier;
import net.ericaro.neojavagen.classes.FormalParameter;
import net.ericaro.neojavagen.classes.FormalParameters;
import net.ericaro.neojavagen.classes.InterfaceTypes;
import net.ericaro.neojavagen.classes.Interfaces;
import net.ericaro.neojavagen.classes.MethodDeclaration;
import net.ericaro.neojavagen.classes.MethodDeclarator;
import net.ericaro.neojavagen.classes.MethodHeader;
import net.ericaro.neojavagen.classes.MethodModifier;
import net.ericaro.neojavagen.classes.NormalClassDeclaration;
import net.ericaro.neojavagen.classes.ReturnMethodDeclarator;
import net.ericaro.neojavagen.classes.SimpleVariableDeclaratorId;
import net.ericaro.neojavagen.classes.Super;
import net.ericaro.neojavagen.classes.SuperExplicitConstructorInvocation;
import net.ericaro.neojavagen.classes.VariableDeclaratorId;
import net.ericaro.neojavagen.classes.VariableDeclarators;
import net.ericaro.neojavagen.classes.VoidMethodDeclarator;
import net.ericaro.neojavagen.expressions.Arguments;
import net.ericaro.neojavagen.expressions.Assignment;
import net.ericaro.neojavagen.expressions.AssignmentOperator;
import net.ericaro.neojavagen.expressions.PrimaryAccess;
import net.ericaro.neojavagen.expressions.QuestionMarkConditionalExpression;
import net.ericaro.neojavagen.expressions.SimpleMethodInvocation;
import net.ericaro.neojavagen.expressions.This;
import net.ericaro.neojavagen.expressions.UnaryExpression;
import net.ericaro.neojavagen.names.QualifiedIdentifier;
import net.ericaro.neojavagen.packages.JavadocComment;
import net.ericaro.neojavagen.packages.TypeDeclaration;
import net.ericaro.neojavagen.types.ClassOrInterfaceType;
import net.ericaro.neojavagen.types.Identifier;
import net.ericaro.neojavagen.types.Type;
import net.ericaro.neojavagen.types.TypedIdentifier;

public class CompositionPart extends AbstractPart {

	/**
	 * any part of a composition
	 * 
	 * @author eric
	 */
	private abstract class Component {
		String name;
		Identifier simpleTypeName;
		boolean optional;
		boolean literal;
		Type type;
		Identifier field;
		UnaryExpression value;
		UnaryExpression option;
		PartReference target;

		void resolve(){
			this.simpleTypeName= Grammar.nameSymbol(name);
			this.field = Grammar.nameSymbol(Introspector.decapitalize(name));
		}

		public abstract String toJavadoc() ;
	}

	/**
	 * another AbstractPart stuff
	 * 
	 * @author eric
	 */
	private class MetaComponent extends Component {

		public MetaComponent(PartReference target, boolean optional) {
			super();
			this.literal = false;
			this.target = target;
			this.optional = optional;
		}

		void resolve() { // should be renamed, as the process is "build the symbolic tree , then, lazily, resolve dependencies 
			//this.name = target.getSimpleTypeName().toString();
			this.type = target.getClassOrInterfaceType();
			this.option = Literals.nullLiteral();
			super.resolve();
		}

		@Override
		public String toJavadoc() {
			return "<{@link "+target.getClassOrInterfaceType()+" "+target.getSimpleTypeName()+"}>"+(optional?"?":"");
		}
		

	}

	/**
	 * a literal part
	 * 
	 * @author eric
	 */
	private class Literal extends Component {
		String symbol;

		public Literal(String value, boolean optional) {
			super();
			this.literal = true;
			this.optional = optional;
			this.symbol = value;
			this.name = optional ? symbol : null;
		}

		void resolve() {
			this.value = Literals.stringLiteral(symbol);
			if (optional) {
				this.type = Grammar.fromJava(Boolean.TYPE);
				this.option = Literals.booleanLiteral(false);
				super.resolve();
			}
		}

		@Override
		public String toJavadoc() {
			return "\""+symbol+"\""+(optional?"?":"");
		}
		
	}

	private List<Component> components;
	

	public CompositionPart(Grammar g, String name) {
		super(g, name);
		this.components = new ArrayList<Component>();
	}

	public CompositionPart withComponent(String name, boolean optional) {
		components.add(new MetaComponent(grammar.createPartReference(name), optional));
		return this;
	}

	public CompositionPart withLiteral(String value, boolean optional) {
		components.add(new Literal(value, optional));

		return this;
	}

	
	
	
	public void resolve() {
		//starts with a simple name
		System.out.println(getSimpleTypeName().toString());
		if ("AssertWithMessage".equals(getSimpleTypeName().toString()))
				System.out.println("resolving "+ getSimpleTypeName()); 
		for (Component c : components)
			if (c.target!=null)
				c.name = c.target.getSimpleTypeName().toString();
		
		// generate unique name
		Map<String, Integer> used = new HashMap<String, Integer>(); // count occurences, to rename fields
		// first pass: count instances, and rename (missing always the first one
		for (Component c : components) {
			if (c.name!=null)
				if (used.containsKey(c.name)) {
					int i = used.get(c.name);
					used.put(c.name, i + 1);
					c.name += i;
				} else
					used.put(c.name, 2);
		}
		// second rename the first occurence
		for (Component c : components)
			if (c.name!=null && used.containsKey(c.name) && used.get(c.name)>2) 
					c.name += "1";
		
		for (Component c : components)
			c.resolve();
		

	}

	private JavadocComment generateJavadoc(){
		StringBuilder sb = new StringBuilder();
		
		sb.append( "<b>");
		sb.append(getSimpleTypeName());
		sb.append("</b>  ::=" );
		for(Component c: components)
			sb.append(" ").append(c.toJavadoc() );
		sb.append("\n *  \n" + 
				" * \n" + 
				" * @author eric@ericaro.net\n" + 
				" *\n" + 
				" ");
		
		return new JavadocComment(Literals.javadocLiteral(sb.toString()));
	}
	
	@Override
	protected TypeDeclaration getTypeDeclaration() {

		ClassBodyDeclarations classBodyDeclarations = new ClassBodyDeclarations();

		// workaround, type declaration sadly handles null interfaces, but not empty ones (it generates an "implements" followed by nothing
		InterfaceTypes interfaceTypes = getInterfaceTypes();
		Interfaces interfaces;
		if (interfaceTypes.size() == 0)
			interfaces = null;
		else
			interfaces = new Interfaces(interfaceTypes);

		
		boolean anyOptional = false; // tells wether or not I need two constructors one with non optional only, one with optionals too
		for (Component c : components)
			if (c.optional)
				anyOptional = true;

		{
			// generate one field for each parameter
			 for (Component c : components) {
				 if (!c.literal || c.optional) {// minimal list
					 classBodyDeclarations.add(new FieldDeclaration(null, null, EnumSet.of(FieldModifier.PRIVATE), c.type, new VariableDeclarators(new
							 SimpleVariableDeclaratorId(new VariableDeclaratorId(c.field)))));
					 
					 addGetterSetter(classBodyDeclarations, c.type, c.field, c.simpleTypeName);
					 
				 }
			}
		 }
		// add classbody content (constructors, and later more methods)
		// two kind of constructor, the minimalistic, and the maximalistic (if there are differences)

		if (anyOptional) {
			// minimalistic only non optional non literal constructor that delegates to the standard one
			FormalParameters formalparameters = new FormalParameters();

			BlockStatements blockstatements = new BlockStatements();
			
			//ThisExplicitConstructorInvocation thisInvocation = new ThisExplicitConstructorInvocation(null, arguments);
			classBodyDeclarations.add(new ConstructorDeclaration(null, null, EnumSet.of(ConstructorModifier.PUBLIC), new ConstructorDeclarator(null, self.getSimpleTypeName(), formalparameters), null, new ConstructorBody(null, blockstatements)));

			for (Component c : components) {
				if (!(c.literal || c.optional)) {// minimal list
					formalparameters.add(new FormalParameter(c.type, new VariableDeclaratorId(c.field)));
					blockstatements.add(new ExpressionStatement(new Assignment(new PrimaryAccess(This.THIS, c.field), AssignmentOperator.EqualsSymbol, Grammar.unaryExpression(c.field))));
					//arguments.add(Grammar.unaryExpression(c.field)); //
				}
				else if (!c.literal || c.optional) {// maximal list, in the this() method
					blockstatements.add(new ExpressionStatement(new Assignment(new PrimaryAccess(This.THIS, c.field), AssignmentOperator.EqualsSymbol, Grammar.unaryExpression(c.option))));
					//arguments.add(Grammar.unaryExpression(c.option)); //
				}

			}
		}
		// always add the maximum size constructor
		{
			FormalParameters formalparameters = new FormalParameters();
			BlockStatements blockstatements = new BlockStatements();
			Arguments arguments = new Arguments();
			SuperExplicitConstructorInvocation superInvocation = new SuperExplicitConstructorInvocation(null, arguments);
			classBodyDeclarations.add(new ConstructorDeclaration(null, null, EnumSet.of(ConstructorModifier.PUBLIC), new ConstructorDeclarator(null, self.getSimpleTypeName(), formalparameters), null, new ConstructorBody(superInvocation, blockstatements)));

			for (Component c : components)
				if (!c.literal || c.optional)  {// maximal list
					formalparameters.add(new FormalParameter(c.type, new VariableDeclaratorId(c.field)));
					blockstatements.add(new ExpressionStatement(new Assignment(new PrimaryAccess(This.THIS, c.field), AssignmentOperator.EqualsSymbol, Grammar.unaryExpression(c.field))));
			}
		}
		
		{// add the toString method
			BlockStatements blockStatements = new BlockStatements();
			Arguments arguments = new Arguments();
			classBodyDeclarations.add(new MethodDeclaration(new MethodHeader(null, null, EnumSet.of(MethodModifier.PUBLIC), null, new ReturnMethodDeclarator(Grammar.fromJava(String.class), new MethodDeclarator(Literals.identifier("toString"))), null), new Block(blockStatements)));
			blockStatements.add( new ReturnStatement( Grammar.unaryExpression((new SimpleMethodInvocation(new QualifiedIdentifier(Literals.identifier("print")), arguments )))));
			
			for (Component c : components) {
				if (!c.literal || c.optional)  {// maximal list
					if (c.literal){
						QuestionMarkConditionalExpression v = new QuestionMarkConditionalExpression(Grammar.unaryExpression(c.field), Grammar.unaryExpression(c.value), Grammar.unaryExpression(Literals.nullLiteral()));
						arguments.add(v); //
					}
					else
						arguments.add(Grammar.unaryExpression(c.field)); //
				}
				else					// switch between literal or field
					arguments.add(Grammar.unaryExpression(c.value)); //
				
			}
			
		}
		
		ClassOrInterfaceType javaPartComposition = new ClassOrInterfaceType();
		for (String i : "net/ericaro/neojavagen/JavaPartComposition".split("/"))
			javaPartComposition.add(new TypedIdentifier( Literals.identifier(i)));
		
		//ClassType javaPartComposition = Grammar.fromJava(JavaPartComposition.class);
		return new NormalClassDeclaration(generateJavadoc(),null,  EnumSet.of(ClassModifier.PUBLIC), self.getSimpleTypeName(), null, new Super(javaPartComposition), interfaces, new ClassBody(classBodyDeclarations));
	}

	
	protected void addGetterSetter(ClassBodyDeclarations bodyDeclarations, Type type, Identifier field, Identifier typename){
		
		
		ReturnStatement getStatement = new ReturnStatement(Grammar.unaryExpression(field));
		BlockStatement setStatement = new ExpressionStatement(new Assignment(new PrimaryAccess(This.THIS, field), AssignmentOperator.EqualsSymbol, Grammar.unaryExpression(field)));
		bodyDeclarations.add(new MethodDeclaration(new MethodHeader(null, null, EnumSet.of(MethodModifier.PUBLIC), null,new ReturnMethodDeclarator(type, new MethodDeclarator(Literals.identifier("get"+typename))),null),new Block(new BlockStatements(getStatement))));
		bodyDeclarations.add(new MethodDeclaration(new MethodHeader(null, null, EnumSet.of(MethodModifier.PUBLIC), null,new VoidMethodDeclarator(new MethodDeclarator(Literals.identifier("set"+typename), new FormalParameters(new FormalParameter(type, new VariableDeclaratorId(field))))),null),new Block(new BlockStatements(setStatement))));
		
	}
	
}
